// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/video/gpu_memory_buffer_video_frame_pool.h"

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <list>
#include <memory>
#include <utility>

#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/containers/stack_container.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_provider.h"
#include "base/trace_event/trace_event.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "media/base/bind_to_current_loop.h"
#include "media/renderers/gpu_video_accelerator_factories.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/buffer_format_util.h"
//#include "ui/gfx/gpu_memory_buffer_tracing.h"
#include "ui/gl/trace_util.h"

namespace media {

// Implementation of a pool of GpuMemoryBuffers used to back VideoFrames.
class GpuMemoryBufferVideoFramePool::PoolImpl
    : public base::RefCountedThreadSafe<
          GpuMemoryBufferVideoFramePool::PoolImpl>,
      public base::trace_event::MemoryDumpProvider {
public:
    // |media_task_runner| is the media task runner associated with the
    // GL context provided by |gpu_factories|
    // |worker_task_runner| is a task runner used to asynchronously copy
    // video frame's planes.
    // |gpu_factories| is an interface to GPU related operation and can be
    // null if a GL context is not available.
    PoolImpl(const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
        const scoped_refptr<base::TaskRunner>& worker_task_runner,
        GpuVideoAcceleratorFactories* gpu_factories)
        : media_task_runner_(media_task_runner)
        , worker_task_runner_(worker_task_runner)
        , gpu_factories_(gpu_factories)
        , output_format_(GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED)
    {
        DCHECK(media_task_runner_);
        DCHECK(worker_task_runner_);
    }

    // Takes a software VideoFrame and calls |frame_ready_cb| with a VideoFrame
    // backed by native textures if possible.
    // The data contained in video_frame is copied into the returned frame
    // asynchronously posting tasks to |worker_task_runner_|, while
    // |frame_ready_cb| will be called on |media_task_runner_| once all the data
    // has been copied.
    void CreateHardwareFrame(const scoped_refptr<VideoFrame>& video_frame,
        const FrameReadyCB& cb);

    bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
        base::trace_event::ProcessMemoryDump* pmd) override;

private:
    friend class base::RefCountedThreadSafe<
        GpuMemoryBufferVideoFramePool::PoolImpl>;
    ~PoolImpl() override;

    // Resource to represent a plane.
    struct PlaneResource {
        gfx::Size size;
        std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer;
        unsigned texture_id = 0u;
        unsigned image_id = 0u;
        gpu::Mailbox mailbox;
    };

    // All the resources needed to compose a frame.
    struct FrameResources {
        explicit FrameResources(const gfx::Size& size)
            : size(size)
        {
        }
        void SetIsInUse(bool in_use) { in_use_ = in_use; }
        bool IsInUse() const { return in_use_; }

        const gfx::Size size;
        PlaneResource plane_resources[VideoFrame::kMaxPlanes];

    private:
        bool in_use_ = true;
    };

    // Copy |video_frame| data into |frame_resouces|
    // and calls |done| when done.
    void CopyVideoFrameToGpuMemoryBuffers(
        const scoped_refptr<VideoFrame>& video_frame,
        FrameResources* frame_resources,
        const FrameReadyCB& frame_ready_cb);

    // Called when all the data has been copied.
    void OnCopiesDone(const scoped_refptr<VideoFrame>& video_frame,
        FrameResources* frame_resources,
        const FrameReadyCB& frame_ready_cb);

    // Prepares GL resources, mailboxes and calls |frame_ready_cb| with the new
    // VideoFrame.
    // This has to be run on |media_task_runner_| where |frame_ready_cb| will also
    // be run.
    void BindAndCreateMailboxesHardwareFrameResources(
        const scoped_refptr<VideoFrame>& video_frame,
        FrameResources* frame_resources,
        const FrameReadyCB& frame_ready_cb);

    // Return true if |resources| can be used to represent a frame for
    // specific |format| and |size|.
    static bool AreFrameResourcesCompatible(const FrameResources* resources,
        const gfx::Size& size)
    {
        return size == resources->size;
    }

    // Get the resources needed for a frame out of the pool, or create them if
    // necessary.
    // This also drops the LRU resources that can't be reuse for this frame.
    FrameResources* GetOrCreateFrameResources(
        const gfx::Size& size,
        GpuVideoAcceleratorFactories::OutputFormat format);

    // Callback called when a VideoFrame generated with GetFrameResources is no
    // longer referenced.
    // This must be called on the thread where |media_task_runner_| is current.
    void MailboxHoldersReleased(FrameResources* frame_resources,
        const gpu::SyncToken& sync_token);

    // Delete resources. This has to be called on the thread where |task_runner|
    // is current.
    static void DeleteFrameResources(GpuVideoAcceleratorFactories* gpu_factories,
        FrameResources* frame_resources);

    // Task runner associated to the GL context provided by |gpu_factories_|.
    scoped_refptr<base::SingleThreadTaskRunner> media_task_runner_;
    // Task runner used to asynchronously copy planes.
    scoped_refptr<base::TaskRunner> worker_task_runner_;

    // Interface to GPU related operations.
    GpuVideoAcceleratorFactories* gpu_factories_;

    // Pool of resources.
    std::list<FrameResources*> resources_pool_;

    GpuVideoAcceleratorFactories::OutputFormat output_format_;

    DISALLOW_COPY_AND_ASSIGN(PoolImpl);
};

namespace {

    // VideoFrame copies to GpuMemoryBuffers will be split in copies where the
    // output size is |kBytesPerCopyTarget| bytes and run in parallel.
    const size_t kBytesPerCopyTarget = 1024 * 1024; // 1MB

    // Return the GpuMemoryBuffer format to use for a specific VideoPixelFormat
    // and plane.
    gfx::BufferFormat GpuMemoryBufferFormat(
        media::GpuVideoAcceleratorFactories::OutputFormat format,
        size_t plane)
    {
        switch (format) {
        case GpuVideoAcceleratorFactories::OutputFormat::I420:
            DCHECK_LE(plane, 2u);
            return gfx::BufferFormat::R_8;
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
            DCHECK_LE(plane, 1u);
            return gfx::BufferFormat::YUV_420_BIPLANAR;
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB:
            DCHECK_LE(plane, 1u);
            return plane == 0 ? gfx::BufferFormat::R_8 : gfx::BufferFormat::RG_88;
        case GpuVideoAcceleratorFactories::OutputFormat::UYVY:
            DCHECK_EQ(0u, plane);
            return gfx::BufferFormat::UYVY_422;
        case GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED:
            NOTREACHED();
            break;
        }
        return gfx::BufferFormat::BGRA_8888;
    }

    unsigned ImageInternalFormat(GpuVideoAcceleratorFactories::OutputFormat format,
        size_t plane)
    {
        switch (format) {
        case GpuVideoAcceleratorFactories::OutputFormat::I420:
            DCHECK_LE(plane, 2u);
            return GL_RED_EXT;
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB:
            DCHECK_LE(plane, 1u);
            return plane == 0 ? GL_RED_EXT : GL_RG_EXT;
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
            DCHECK_LE(plane, 1u);
            return GL_RGB_YCBCR_420V_CHROMIUM;
        case GpuVideoAcceleratorFactories::OutputFormat::UYVY:
            DCHECK_EQ(0u, plane);
            return GL_RGB_YCBCR_422_CHROMIUM;
        case GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED:
            NOTREACHED();
            break;
        }
        return 0;
    }

    // The number of output planes to be copied in each iteration.
    size_t PlanesPerCopy(GpuVideoAcceleratorFactories::OutputFormat format)
    {
        switch (format) {
        case GpuVideoAcceleratorFactories::OutputFormat::I420:
        case GpuVideoAcceleratorFactories::OutputFormat::UYVY:
            return 1;
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB:
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
            return 2;
        case GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED:
            NOTREACHED();
            break;
        }
        return 0;
    }

    VideoPixelFormat VideoFormat(
        GpuVideoAcceleratorFactories::OutputFormat format)
    {
        switch (format) {
        case GpuVideoAcceleratorFactories::OutputFormat::I420:
            return PIXEL_FORMAT_I420;
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB:
            return PIXEL_FORMAT_NV12;
        case GpuVideoAcceleratorFactories::OutputFormat::UYVY:
            return PIXEL_FORMAT_UYVY;
        case GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED:
            NOTREACHED();
            break;
        }
        return PIXEL_FORMAT_UNKNOWN;
    }

    VideoPixelFormat FinalVideoFormat(
        GpuVideoAcceleratorFactories::OutputFormat format)
    {
        // Consumers should sample from NV12 textures as if they're XRGB.
        if (format == GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB)
            return PIXEL_FORMAT_XRGB;
        return VideoFormat(format);
    }

    // The number of output planes to be copied in each iteration.
    size_t NumGpuMemoryBuffers(GpuVideoAcceleratorFactories::OutputFormat format)
    {
        return VideoFrame::NumPlanes(FinalVideoFormat(format));
    }

    // The number of output rows to be copied in each iteration.
    int RowsPerCopy(size_t plane, VideoPixelFormat format, int width)
    {
        int bytes_per_row = VideoFrame::RowBytes(plane, format, width);
        if (format == PIXEL_FORMAT_NV12) {
            DCHECK_EQ(0u, plane);
            bytes_per_row += VideoFrame::RowBytes(1, format, width);
        }
        // Copy an even number of lines, and at least one.
        return std::max<size_t>((kBytesPerCopyTarget / bytes_per_row) & ~1, 1);
    }

    void CopyRowsToI420Buffer(int first_row,
        int rows,
        int bytes_per_row,
        const uint8_t* source,
        int source_stride,
        uint8_t* output,
        int dest_stride,
        const base::Closure& done)
    {
        TRACE_EVENT2("media", "CopyRowsToI420Buffer", "bytes_per_row", bytes_per_row,
            "rows", rows);
        if (output) {
            DCHECK_NE(dest_stride, 0);
            DCHECK_LE(bytes_per_row, std::abs(dest_stride));
            DCHECK_LE(bytes_per_row, source_stride);

            libyuv::CopyPlane(source + source_stride * first_row, source_stride,
                output + dest_stride * first_row, dest_stride,
                bytes_per_row, rows);
        }
        done.Run();
    }

    void CopyRowsToNV12Buffer(int first_row,
        int rows,
        int bytes_per_row,
        const scoped_refptr<VideoFrame>& source_frame,
        uint8_t* dest_y,
        int dest_stride_y,
        uint8_t* dest_uv,
        int dest_stride_uv,
        const base::Closure& done)
    {
        TRACE_EVENT2("media", "CopyRowsToNV12Buffer", "bytes_per_row", bytes_per_row,
            "rows", rows);
        if (dest_y && dest_uv) {
            DCHECK_NE(dest_stride_y, 0);
            DCHECK_NE(dest_stride_uv, 0);
            DCHECK_LE(bytes_per_row, std::abs(dest_stride_y));
            DCHECK_LE(bytes_per_row, std::abs(dest_stride_uv));
            DCHECK_EQ(0, first_row % 2);

            libyuv::I420ToNV12(
                source_frame->visible_data(VideoFrame::kYPlane) + first_row * source_frame->stride(VideoFrame::kYPlane),
                source_frame->stride(VideoFrame::kYPlane),
                source_frame->visible_data(VideoFrame::kUPlane) + first_row / 2 * source_frame->stride(VideoFrame::kUPlane),
                source_frame->stride(VideoFrame::kUPlane),
                source_frame->visible_data(VideoFrame::kVPlane) + first_row / 2 * source_frame->stride(VideoFrame::kVPlane),
                source_frame->stride(VideoFrame::kVPlane),
                dest_y + first_row * dest_stride_y, dest_stride_y,
                dest_uv + first_row / 2 * dest_stride_uv, dest_stride_uv, bytes_per_row,
                rows);
        }
        done.Run();
    }

    void CopyRowsToUYVYBuffer(int first_row,
        int rows,
        int width,
        const scoped_refptr<VideoFrame>& source_frame,
        uint8_t* output,
        int dest_stride,
        const base::Closure& done)
    {
        TRACE_EVENT2("media", "CopyRowsToUYVYBuffer", "bytes_per_row", width * 2,
            "rows", rows);
        if (output) {
            DCHECK_NE(dest_stride, 0);
            DCHECK_LE(width, std::abs(dest_stride / 2));
            DCHECK_EQ(0, first_row % 2);
            libyuv::I420ToUYVY(
                source_frame->visible_data(VideoFrame::kYPlane) + first_row * source_frame->stride(VideoFrame::kYPlane),
                source_frame->stride(VideoFrame::kYPlane),
                source_frame->visible_data(VideoFrame::kUPlane) + first_row / 2 * source_frame->stride(VideoFrame::kUPlane),
                source_frame->stride(VideoFrame::kUPlane),
                source_frame->visible_data(VideoFrame::kVPlane) + first_row / 2 * source_frame->stride(VideoFrame::kVPlane),
                source_frame->stride(VideoFrame::kVPlane),
                output + first_row * dest_stride, dest_stride, width, rows);
        }
        done.Run();
    }

    gfx::Size CodedSize(const scoped_refptr<VideoFrame>& video_frame,
        GpuVideoAcceleratorFactories::OutputFormat output_format)
    {
        DCHECK(gfx::Rect(video_frame->coded_size())
                   .Contains(video_frame->visible_rect()));
        DCHECK((video_frame->visible_rect().x() & 1) == 0);
        gfx::Size output;
        switch (output_format) {
        case GpuVideoAcceleratorFactories::OutputFormat::I420:
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
        case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB:
            DCHECK((video_frame->visible_rect().y() & 1) == 0);
            output = gfx::Size((video_frame->visible_rect().width() + 1) & ~1,
                (video_frame->visible_rect().height() + 1) & ~1);
            break;
        case GpuVideoAcceleratorFactories::OutputFormat::UYVY:
            output = gfx::Size((video_frame->visible_rect().width() + 1) & ~1,
                video_frame->visible_rect().height());
            break;
        case GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED:
            NOTREACHED();
        }
        DCHECK(gfx::Rect(video_frame->coded_size()).Contains(gfx::Rect(output)));
        return output;
    }
} // unnamed namespace

// Creates a VideoFrame backed by native textures starting from a software
// VideoFrame.
// The data contained in |video_frame| is copied into the VideoFrame passed to
// |frame_ready_cb|.
// This has to be called on the thread where |media_task_runner_| is current.
void GpuMemoryBufferVideoFramePool::PoolImpl::CreateHardwareFrame(
    const scoped_refptr<VideoFrame>& video_frame,
    const FrameReadyCB& frame_ready_cb)
{
    DCHECK(media_task_runner_->BelongsToCurrentThread());
    // Lazily initialize output_format_ since VideoFrameOutputFormat() has to be
    // called on the media_thread while this object might be instantiated on any.
    if (output_format_ == GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED)
        output_format_ = gpu_factories_->VideoFrameOutputFormat();

    if (output_format_ == GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED) {
        frame_ready_cb.Run(video_frame);
        return;
    }
    switch (video_frame->format()) {
    // Supported cases.
    case PIXEL_FORMAT_YV12:
    case PIXEL_FORMAT_I420:
        break;
    // Unsupported cases.
    case PIXEL_FORMAT_YV12A:
    case PIXEL_FORMAT_YV16:
    case PIXEL_FORMAT_YV24:
    case PIXEL_FORMAT_NV12:
    case PIXEL_FORMAT_NV21:
    case PIXEL_FORMAT_UYVY:
    case PIXEL_FORMAT_YUY2:
    case PIXEL_FORMAT_ARGB:
    case PIXEL_FORMAT_XRGB:
    case PIXEL_FORMAT_RGB24:
    case PIXEL_FORMAT_RGB32:
    case PIXEL_FORMAT_MJPEG:
    case PIXEL_FORMAT_MT21:
    case PIXEL_FORMAT_YUV420P9:
    case PIXEL_FORMAT_YUV422P9:
    case PIXEL_FORMAT_YUV444P9:
    case PIXEL_FORMAT_YUV420P10:
    case PIXEL_FORMAT_YUV422P10:
    case PIXEL_FORMAT_YUV444P10:
    case PIXEL_FORMAT_YUV420P12:
    case PIXEL_FORMAT_YUV422P12:
    case PIXEL_FORMAT_YUV444P12:
    case PIXEL_FORMAT_Y8:
    case PIXEL_FORMAT_Y16:
    case PIXEL_FORMAT_I422:
    case PIXEL_FORMAT_UNKNOWN:
        frame_ready_cb.Run(video_frame);
        return;
    }

    const gfx::Size coded_size = CodedSize(video_frame, output_format_);
    // Acquire resources. Incompatible ones will be dropped from the pool.
    FrameResources* frame_resources = GetOrCreateFrameResources(coded_size, output_format_);
    if (!frame_resources) {
        frame_ready_cb.Run(video_frame);
        return;
    }

    worker_task_runner_->PostTask(
        FROM_HERE, base::Bind(&PoolImpl::CopyVideoFrameToGpuMemoryBuffers, this, video_frame, frame_resources, frame_ready_cb));
}

bool GpuMemoryBufferVideoFramePool::PoolImpl::OnMemoryDump(
    const base::trace_event::MemoryDumpArgs& args,
    base::trace_event::ProcessMemoryDump* pmd)
{
//     const uint64_t tracing_process_id = base::trace_event::MemoryDumpManager::GetInstance()
//                                             ->GetTracingProcessId();
//     const int kImportance = 2;
//     for (const FrameResources* frame_resources : resources_pool_) {
//         for (const PlaneResource& plane_resource :
//             frame_resources->plane_resources) {
//             if (plane_resource.gpu_memory_buffer) {
//                 gfx::GpuMemoryBufferId buffer_id = plane_resource.gpu_memory_buffer->GetId();
//                 std::string dump_name = base::StringPrintf(
//                     "media/video_frame_memory/buffer_%d", buffer_id.id);
//                 base::trace_event::MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name);
//                 size_t buffer_size_in_bytes = gfx::BufferSizeForBufferFormat(
//                     plane_resource.size, plane_resource.gpu_memory_buffer->GetFormat());
//                 dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
//                     base::trace_event::MemoryAllocatorDump::kUnitsBytes,
//                     buffer_size_in_bytes);
//                 dump->AddScalar("free_size",
//                     base::trace_event::MemoryAllocatorDump::kUnitsBytes,
//                     frame_resources->IsInUse() ? 0 : buffer_size_in_bytes);
//                 base::trace_event::MemoryAllocatorDumpGuid shared_buffer_guid = gfx::GetGpuMemoryBufferGUIDForTracing(tracing_process_id,
//                     buffer_id);
//                 pmd->CreateSharedGlobalAllocatorDump(shared_buffer_guid);
//                 pmd->AddOwnershipEdge(dump->guid(), shared_buffer_guid, kImportance);
//             }
//         }
//     }
    DebugBreak();
    return true;
}

void GpuMemoryBufferVideoFramePool::PoolImpl::OnCopiesDone(
    const scoped_refptr<VideoFrame>& video_frame,
    FrameResources* frame_resources,
    const FrameReadyCB& frame_ready_cb)
{
    for (const auto& plane_resource : frame_resources->plane_resources) {
        if (plane_resource.gpu_memory_buffer) {
            plane_resource.gpu_memory_buffer->Unmap();
            //plane_resource.gpu_memory_buffer->SetColorSpaceForScanout(video_frame->ColorSpace());
            DebugBreak();
        }
    }

    media_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&PoolImpl::BindAndCreateMailboxesHardwareFrameResources, this,
            video_frame, frame_resources, frame_ready_cb));
}

// Copies |video_frame| into |frame_resources| asynchronously, posting n tasks
// that will be synchronized by a barrier.
// After the barrier is passed OnCopiesDone will be called.
void GpuMemoryBufferVideoFramePool::PoolImpl::CopyVideoFrameToGpuMemoryBuffers(
    const scoped_refptr<VideoFrame>& video_frame,
    FrameResources* frame_resources,
    const FrameReadyCB& frame_ready_cb)
{
    // Compute the number of tasks to post and create the barrier.
    const size_t num_planes = VideoFrame::NumPlanes(VideoFormat(output_format_));
    const size_t planes_per_copy = PlanesPerCopy(output_format_);
    const gfx::Size coded_size = CodedSize(video_frame, output_format_);
    size_t copies = 0;
    for (size_t i = 0; i < num_planes; i += planes_per_copy) {
        const int rows = VideoFrame::Rows(i, VideoFormat(output_format_), coded_size.height());
        const int rows_per_copy = RowsPerCopy(i, VideoFormat(output_format_), coded_size.width());
        copies += rows / rows_per_copy;
        if (rows % rows_per_copy)
            ++copies;
    }

    const base::Closure copies_done = base::Bind(&PoolImpl::OnCopiesDone, this, video_frame, frame_resources,
        frame_ready_cb);
    const base::Closure barrier = base::BarrierClosure(copies, copies_done);

    // Map the buffers.
    for (size_t i = 0; i < NumGpuMemoryBuffers(output_format_); i++) {
        gfx::GpuMemoryBuffer* buffer = frame_resources->plane_resources[i].gpu_memory_buffer.get();

        if (!buffer || !buffer->Map()) {
            DLOG(ERROR) << "Could not get or Map() buffer";
            return;
        }
    }

    // Post all the async tasks.
    for (size_t i = 0; i < num_planes; i += planes_per_copy) {
        gfx::GpuMemoryBuffer* buffer = frame_resources->plane_resources[i].gpu_memory_buffer.get();
        const int rows = VideoFrame::Rows(i, VideoFormat(output_format_), coded_size.height());
        const int rows_per_copy = RowsPerCopy(i, VideoFormat(output_format_), coded_size.width());

        for (int row = 0; row < rows; row += rows_per_copy) {
            const int rows_to_copy = std::min(rows_per_copy, rows - row);
            switch (output_format_) {
            case GpuVideoAcceleratorFactories::OutputFormat::I420: {
                const int bytes_per_row = VideoFrame::RowBytes(
                    i, VideoFormat(output_format_), coded_size.width());
                worker_task_runner_->PostTask(
                    FROM_HERE, base::Bind(&CopyRowsToI420Buffer, row, rows_to_copy, bytes_per_row, video_frame->visible_data(i), video_frame->stride(i), static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0), barrier));
                break;
            }
            case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
                worker_task_runner_->PostTask(
                    FROM_HERE, base::Bind(&CopyRowsToNV12Buffer, row, rows_to_copy, coded_size.width(), video_frame, static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0), static_cast<uint8_t*>(buffer->memory(1)), buffer->stride(1), barrier));
                break;
            case GpuVideoAcceleratorFactories::OutputFormat::NV12_DUAL_GMB: {
                gfx::GpuMemoryBuffer* buffer2 = frame_resources->plane_resources[1].gpu_memory_buffer.get();
                worker_task_runner_->PostTask(
                    FROM_HERE, base::Bind(&CopyRowsToNV12Buffer, row, rows_to_copy, coded_size.width(), video_frame, static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0), static_cast<uint8_t*>(buffer2->memory(0)), buffer2->stride(0), barrier));
                break;
            }

            case GpuVideoAcceleratorFactories::OutputFormat::UYVY:
                worker_task_runner_->PostTask(
                    FROM_HERE, base::Bind(&CopyRowsToUYVYBuffer, row, rows_to_copy, coded_size.width(), video_frame, static_cast<uint8_t*>(buffer->memory(0)), buffer->stride(0), barrier));
                break;
            case GpuVideoAcceleratorFactories::OutputFormat::UNDEFINED:
                NOTREACHED();
            }
        }
    }
}

void GpuMemoryBufferVideoFramePool::PoolImpl::
    BindAndCreateMailboxesHardwareFrameResources(
        const scoped_refptr<VideoFrame>& video_frame,
        FrameResources* frame_resources,
        const FrameReadyCB& frame_ready_cb)
{
    std::unique_ptr<GpuVideoAcceleratorFactories::ScopedGLContextLock> lock(
        gpu_factories_->GetGLContextLock());
    if (!lock) {
        frame_ready_cb.Run(video_frame);
        return;
    }
    gpu::gles2::GLES2Interface* gles2 = lock->ContextGL();

    const gfx::Size coded_size = CodedSize(video_frame, output_format_);
    gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes];
    // Set up the planes creating the mailboxes needed to refer to the textures.
    for (size_t i = 0; i < NumGpuMemoryBuffers(output_format_); i++) {
        PlaneResource& plane_resource = frame_resources->plane_resources[i];
        const gfx::BufferFormat buffer_format = GpuMemoryBufferFormat(output_format_, i);
        unsigned texture_target = gpu_factories_->ImageTextureTarget(buffer_format);
        // Bind the texture and create or rebind the image.
        gles2->BindTexture(texture_target, plane_resource.texture_id);
        if (plane_resource.gpu_memory_buffer && !plane_resource.image_id) {
            const size_t width = VideoFrame::Columns(i, VideoFormat(output_format_),
                coded_size.width());
            const size_t height = VideoFrame::Rows(i, VideoFormat(output_format_), coded_size.height());
            plane_resource.image_id = gles2->CreateImageCHROMIUM(
                plane_resource.gpu_memory_buffer->AsClientBuffer(), width, height,
                ImageInternalFormat(output_format_, i));
        } else if (plane_resource.image_id) {
            gles2->ReleaseTexImage2DCHROMIUM(texture_target, plane_resource.image_id);
        }
        if (plane_resource.image_id)
            gles2->BindTexImage2DCHROMIUM(texture_target, plane_resource.image_id);
        mailbox_holders[i] = gpu::MailboxHolder(plane_resource.mailbox,
            gpu::SyncToken(), texture_target);
    }

    // Insert a sync_token, this is needed to make sure that the textures the
    // mailboxes refer to will be used only after all the previous commands posted
    // in the command buffer have been processed.
    const GLuint64 fence_sync = gles2->InsertFenceSyncCHROMIUM();
    gles2->OrderingBarrierCHROMIUM();

    gpu::SyncToken sync_token;
    gles2->GenUnverifiedSyncTokenCHROMIUM(fence_sync, sync_token.GetData());
    for (size_t i = 0; i < NumGpuMemoryBuffers(output_format_); i++)
        mailbox_holders[i].sync_token = sync_token;

    auto release_mailbox_callback = BindToCurrentLoop(
        base::Bind(&PoolImpl::MailboxHoldersReleased, this, frame_resources));

    VideoPixelFormat frame_format = FinalVideoFormat(output_format_);

    // Create the VideoFrame backed by native textures.
    gfx::Size visible_size = video_frame->visible_rect().size();
    scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
        frame_format, mailbox_holders, release_mailbox_callback, coded_size,
        gfx::Rect(visible_size), video_frame->natural_size(),
        video_frame->timestamp());

    if (!frame) {
        release_mailbox_callback.Run(gpu::SyncToken());
        frame_ready_cb.Run(video_frame);
        return;
    }

    frame->set_color_space(video_frame->ColorSpace());

    bool allow_overlay = false;
    switch (output_format_) {
    case GpuVideoAcceleratorFactories::OutputFormat::I420:
        allow_overlay = video_frame->metadata()->IsTrue(VideoFrameMetadata::ALLOW_OVERLAY);
        break;
    case GpuVideoAcceleratorFactories::OutputFormat::NV12_SINGLE_GMB:
    case GpuVideoAcceleratorFactories::OutputFormat::UYVY:
        allow_overlay = true;
        break;
    default:
        break;
    }
    frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY,
        allow_overlay);

    base::TimeTicks render_time;
    if (video_frame->metadata()->GetTimeTicks(VideoFrameMetadata::REFERENCE_TIME,
            &render_time)) {
        frame->metadata()->SetTimeTicks(VideoFrameMetadata::REFERENCE_TIME,
            render_time);
    }

    frame->metadata()->SetBoolean(VideoFrameMetadata::READ_LOCK_FENCES_ENABLED,
        true);

    frame_ready_cb.Run(frame);
}

// Destroy all the resources posting one task per FrameResources
// to the |media_task_runner_|.
GpuMemoryBufferVideoFramePool::PoolImpl::~PoolImpl()
{
    // Delete all the resources on the media thread.
    while (!resources_pool_.empty()) {
        FrameResources* frame_resources = resources_pool_.front();
        resources_pool_.pop_front();
        media_task_runner_->PostTask(
            FROM_HERE, base::Bind(&PoolImpl::DeleteFrameResources, gpu_factories_, base::Owned(frame_resources)));
    }
}

// Tries to find the resources in the pool or create them.
// Incompatible resources will be dropped.
GpuMemoryBufferVideoFramePool::PoolImpl::FrameResources*
GpuMemoryBufferVideoFramePool::PoolImpl::GetOrCreateFrameResources(
    const gfx::Size& size,
    GpuVideoAcceleratorFactories::OutputFormat format)
{
    auto it = resources_pool_.begin();
    while (it != resources_pool_.end()) {
        FrameResources* frame_resources = *it;
        if (!frame_resources->IsInUse()) {
            if (AreFrameResourcesCompatible(frame_resources, size)) {
                frame_resources->SetIsInUse(true);
                return frame_resources;
            } else {
                resources_pool_.erase(it++);
                DeleteFrameResources(gpu_factories_, frame_resources);
                delete frame_resources;
            }
        } else {
            it++;
        }
    }

    // Create the resources.
    std::unique_ptr<GpuVideoAcceleratorFactories::ScopedGLContextLock> lock(
        gpu_factories_->GetGLContextLock());
    if (!lock)
        return nullptr;

    gpu::gles2::GLES2Interface* gles2 = lock->ContextGL();
    gles2->ActiveTexture(GL_TEXTURE0);
    FrameResources* frame_resources = new FrameResources(size);
    resources_pool_.push_back(frame_resources);
    for (size_t i = 0; i < NumGpuMemoryBuffers(output_format_); i++) {
        PlaneResource& plane_resource = frame_resources->plane_resources[i];
        const size_t width = VideoFrame::Columns(i, VideoFormat(format), size.width());
        const size_t height = VideoFrame::Rows(i, VideoFormat(format), size.height());
        plane_resource.size = gfx::Size(width, height);

        const gfx::BufferFormat buffer_format = GpuMemoryBufferFormat(format, i);
        plane_resource.gpu_memory_buffer = gpu_factories_->CreateGpuMemoryBuffer(
            plane_resource.size, buffer_format,
            gfx::BufferUsage::GPU_READ_CPU_READ_WRITE);

        unsigned texture_target = gpu_factories_->ImageTextureTarget(buffer_format);
        gles2->GenTextures(1, &plane_resource.texture_id);
        gles2->BindTexture(texture_target, plane_resource.texture_id);
        gles2->TexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        gles2->TexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        gles2->TexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        gles2->TexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        gles2->GenMailboxCHROMIUM(plane_resource.mailbox.name);
        gles2->ProduceTextureCHROMIUM(texture_target, plane_resource.mailbox.name);
    }
    return frame_resources;
}

// static
void GpuMemoryBufferVideoFramePool::PoolImpl::DeleteFrameResources(
    GpuVideoAcceleratorFactories* gpu_factories,
    FrameResources* frame_resources)
{
    // TODO(dcastagna): As soon as the context lost is dealt with in media,
    // make sure that we won't execute this callback (use a weak pointer to
    // the old context).

    std::unique_ptr<GpuVideoAcceleratorFactories::ScopedGLContextLock> lock(
        gpu_factories->GetGLContextLock());
    if (!lock)
        return;
    gpu::gles2::GLES2Interface* gles2 = lock->ContextGL();

    for (PlaneResource& plane_resource : frame_resources->plane_resources) {
        if (plane_resource.image_id)
            gles2->DestroyImageCHROMIUM(plane_resource.image_id);
        if (plane_resource.texture_id)
            gles2->DeleteTextures(1, &plane_resource.texture_id);
    }
}

// Called when a VideoFrame is no longer referenced.
// Put back the resources in the pool.
void GpuMemoryBufferVideoFramePool::PoolImpl::MailboxHoldersReleased(
    FrameResources* frame_resources,
    const gpu::SyncToken& release_sync_token)
{
    DCHECK(media_task_runner_->BelongsToCurrentThread());
    auto it = std::find(resources_pool_.begin(), resources_pool_.end(),
        frame_resources);
    DCHECK(it != resources_pool_.end());
    // We want the pool to behave in a FIFO way.
    // This minimizes the chances of locking the buffer that might be
    // still needed for drawing.
    std::swap(*it, resources_pool_.back());
    frame_resources->SetIsInUse(false);
}

GpuMemoryBufferVideoFramePool::GpuMemoryBufferVideoFramePool() { }

GpuMemoryBufferVideoFramePool::GpuMemoryBufferVideoFramePool(
    const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
    const scoped_refptr<base::TaskRunner>& worker_task_runner,
    GpuVideoAcceleratorFactories* gpu_factories)
    : pool_impl_(
        new PoolImpl(media_task_runner, worker_task_runner, gpu_factories))
{
    base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
        pool_impl_.get(), "GpuMemoryBufferVideoFramePool", media_task_runner);
}

GpuMemoryBufferVideoFramePool::~GpuMemoryBufferVideoFramePool()
{
    base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
        pool_impl_.get());
}

void GpuMemoryBufferVideoFramePool::MaybeCreateHardwareFrame(
    const scoped_refptr<VideoFrame>& video_frame,
    const FrameReadyCB& frame_ready_cb)
{
    DCHECK(video_frame);
    pool_impl_->CreateHardwareFrame(video_frame, frame_ready_cb);
}

} // namespace media
